import tkinter as tk
import tkinter.ttk as ttk
import threading as thr
from pathlib import Path
import os
import re
import subprocess
import ftplib
import binascii
import shutil
from ftplib import FTP
import datetime

class RFtp:

    ftp = FTP()

    def __init__(self,host,port=21):
        self.ftp.connect(host,port)

    def login(self,username,pwd):
        self.ftp.set_debuglevel(0)
        self.ftp.login(username,pwd)
        print(self.ftp.welcome)

    def upload_file(self, localpath, remotepath, filename):
        os.chdir(localpath)   # change work dir
        self.ftp.cwd(remotepath)   # remote ftp dir
        file_handle = open(filename,"rb")   # open in write mode
        self.ftp.storbinary('STOR %s' % os.path.basename(filename),file_handle,blocksize=1024)  # unload file

    def download_file(self,localpath,remotepath,filename):
        os.chdir(localpath)   # change work dir
        self.ftp.cwd( remotepath)   # remote ftp dir
        self.ftp.nlst()  # get files in remote dir
        file_handle = open(filename,"wb").write   # open in write mode
        self.ftp.retrbinary('RETR %s' % os.path.basename(filename),file_handle,blocksize=1024)  # download file

    def is_dir(self, name):
        try:
            self.ftp.cwd(name)
        except:
            return False
        else:
            self.ftp.cwd('..')
            return True

    def is_file(self, name):
        return not self.is_dir(name)

    def search_and_get_dir_path(self, start_path:str, dir_name: str):
        self.ftp.cwd(start_path)
        lines = []
        self.ftp.retrlines('LIST', lines.append)
        entries = []
        for li in lines:
            entries.append(li.split()[-1])
        for ent in entries:
            if ent == dir_name and self.is_dir(ent):
                return '/'.join([start_path, ent])
        # recurse to find the dir
        for ent in entries:
            if ent != '.' and ent != '..' and self.is_dir(ent):
                res = self.search_and_get_dir_path(('/'.join([start_path, ent])), dir_name)
                if res is not None:
                    return res
                else:
                    self.ftp.cwd('..')

        return None

    def search_and_get_file_path(self, start_path:str, file_name: str):
        self.ftp.cwd(start_path)
        lines = []
        self.ftp.retrlines('LIST', lines.append)
        entries = []
        for li in lines:
            entries.append(li.split()[-1])
        for ent in entries:
            if ent == file_name and self.is_file(ent):
                return '/'.join([start_path, ent])
        # recurse to find the file
        for ent in entries:
            if ent != '.' and ent != '..' and self.is_dir(ent):
                res = self.search_and_get_file_path(('/'.join([start_path, ent])), file_name)
                if res is not None:
                    return res
                else:
                    self.ftp.cwd('..')

        return None

    def close(self):
        self.ftp.set_debuglevel(0)
        self.ftp.quit()


class ReleaseController:
    FTP_HOST = '172.22.102.202'
    FTP_USERNAME = 'wangqiang'
    FTP_PASSWORD = 'wangqiang'
    ''' This class maintains all release functions '''
    def __init__(self, dir: Path):
        ''' dir: release directory '''
        # some path macros
        self.CUST_INC_DIR = 'core/code/customer/dlinkme/inc/'
        self.REPO_MODEL = ('ENV', 'BOOT', 'OS', 'SDK', 'ICORE', 'CUS')
        self.FTP_FIRMWARE_PATH = '/Project/Switch/DLINK-ME/Firmware/'
        self.CAMEOTAG_IN_HEX = '43414d454f544147' # 'CAMEOTAG'

        # Special fw version, no HW type
        self.special_model_name = ['WS5-DGS-1210-12TS', 'WS5-DGS-1210-28X', 'WS5-DGS-1210-28XS']

        self.global_new_feature = ''
        self.global_bug_fixed = ''
        self.release_dir = dir
        self.models_dir_dict = {}       # {model_dir : <model_name>}
        self.models_name_dict = {}      # {model_name : <model_dir>}
        self.model_repos_branch = {}    # {model_name : {repo_name:(<repo>, <branch>)}}
        self.model_params = {}
        '''
        {model_name : 
            {
                {fw_version : {version_tail : <version_tail>, version_dot : <version_dot>, version_hori_line : <version_hori_line>}},
                {hw_version : {version_tail : <version_tail>, version_dot : <version_dot>}},
                {show_model_name : {hori_line_with_slash : <hori_line_with_slash>, hori_line_no_slash : <hori_line_no_slash>}},
                {mib_hw_version : <mib_hw_version>},
                {mib_sw_version : {version_dot : <version_dot>, version_hori_line : <version_hori_line>}},
                {git_info : <git_info>},
                {bug_fixed : <bug_fixed>},
                {new_feature : <new_feature>}
                {checksum_prom : <checksum_prom>},
                {checksum_runtime : <checksum_runtime>}
                {release_by : <release_by>}
            }
        }
        '''

        self.hex_path_dict = {}
        '''
        {model_name :
            {
                base_name: # just store the tail base name
                    {
                        fw_hex : <fw_hex>,
                        cmds_csv : <cmds_csv>,
                        release_note_txt : <release_note_txt>,
                        fw_mib : <fw_mib>,
                        fw_bin : <fw_bin>,
                        iss_exe : <iss_exe>,
                        all_hex : <all_hex>
                    },
                src :  # store the full path
                    {
                        fw_hex : {path : <fw_hex>, is_backup : <bool>, is_done : <bool>},
                        cmds_csv : {path : <cmds_csv>, is_backup : <bool>, is_done : <bool>},
                        release_note_txt : {path : <release_note_txt>, is_backup : <bool>, is_done : <bool>},
                        fw_mib : {path : <fw_mib>, is_backup : <bool>, is_done : <bool>},
                        fw_bin : {path : <fw_bin>, is_backup : <bool>, is_done : <bool>},
                        iss_exe : {path : <iss_exe>, is_backup : <bool>, is_done : <bool>},
                        all_hex : {path : <all_hex>, is_backup : <bool>, is_done : <bool>},
                    }
                dst : # store the full path
                    {
                        fw_hex : <fw_hex>,
                        cmds_csv : <cmds_csv>,
                        release_note_txt : <release_note_txt>,
                        fw_mib : <fw_mib>,
                        fw_bin : <fw_bin>,
                        iss_exe : <iss_exe>,
                        all_hex : <all_hex>
                    } 
            }
        }
        '''

        self.ftp_path_dict = {}
        '''
        {model_name :
            {
                # for getting the last release notes
                version_tail : <version_tail>
                last_version_tail : <last_version_tail>
                last_version_normal : <last_version_normal>
                last_all_hex : <last_all_hex>
                normal : <normal>,
                backup : <backup>,
                last_release_note_path : <path>
                cur_all_hex_path : <path>
            }
        }
        '''

        self.ftp_path_reachable = {}
        '''
        {model_name :
            {
                normal_hex : <bool>,
                backup_hex : <bool>,
                release_note : <bool>,
                all_hex : <bool>
            }
        }
        '''

        self.release_settings_dict = {}
        '''
        {
            release_by : <name>,
            release_to : <directory>
            ftp_host : <host>,
            ftp_username : <username>,
            ftp_password : <password>,
            model_name : {
                need_release : <ttk_variable>
            }
        }
        '''

    def init_ftp_path_reachable_dict(self):
        for name in self.models_name_dict:
            self.ftp_path_reachable[name] = \
                {'normal_hex' : False, 'backup_hex' : False,
                'release_note' : False, 'all_hex' : False}

    def init_dict(self):
        self.init_model_params_dict()
        self.init_model_repo_branch_dict()
        self.init_ftp_path_reachable_dict()

    def is_ftp_normal_hex_reachable(self, model_name: str):
        return self.ftp_path_reachable[model_name].get('normal_hex') is True

    def is_ftp_backup_hex_reachable(self, model_name: str):
        return self.ftp_path_reachable[model_name].get('backup_hex') is True

    def is_ftp_release_note_reachable(self, model_name: str):
        return self.ftp_path_reachable[model_name].get('release_note') is True

    def is_ftp_all_hex_reachable(self, model_name: str):
        return self.ftp_path_reachable[model_name].get('all_hex') is True

    def set_ftp_normal_hex_reachable(self, model_name: str, reachable: bool):
        self.ftp_path_reachable[model_name]['normal_hex'] = reachable

    def set_ftp_backup_hex_reachable(self, model_name: str, reachable: bool):
        self.ftp_path_reachable[model_name]['backup_hex'] = reachable

    def set_ftp_release_note_reachable(self, model_name: str, reachable: bool):
        self.ftp_path_reachable[model_name]['release_note'] = reachable

    def set_ftp_all_hex_reachable(self, model_name: str, reachable: bool):
        self.ftp_path_reachable[model_name]['all_hex'] = reachable

    def get_model_hex_list(self, model_name):
        list = []
        for (nm, pt) in self.hex_path_dict[model_name]['dst'].items():
            list.append(pt)
        return list

    def get_global_new_feature(self):
        return self.global_new_feature

    def get_global_bug_fixed(self):
        return self.global_bug_fixed

    def set_global_new_feature(self, content:str):
        self.global_new_feature = content

    def set_global_bug_fixed(self, content:str):
        self.global_bug_fixed = content

    def get_new_feature(self, model_name:str):
        res = self.model_params[model_name].get('new_feature')
        if res is not None:
            return res
        return ''
    
    def get_bug_fixed(self, model_name:str):
        res = self.model_params[model_name].get('bug_fixed')
        if res is not None:
            return res
        return ''

    def set_new_feature(self, model_name:str, text:str):
        self.model_params[model_name]['new_feature'] = text

    def set_new_feature_lock(self, model_name:str):
        self.model_params[model_name]['new_feature_locked'] = True

    def is_new_feature_locked(self, model_name:str):
        return self.model_params[model_name]['new_feature_locked']

    def set_bug_fixed(self, model_name:str, text:str):
        self.model_params[model_name]['bug_fixed'] = text

    def set_bug_fixed_lock(self, model_name:str):
        self.model_params[model_name]['bug_fixed_locked'] = True

    def is_bug_fixed_locked(self, model_name:str):
        return self.model_params[model_name]['bug_fixed_locked']

    def get_hw_version_tail(self, model_name:str):
        return self.model_params[model_name]['hw_version']['version_tail']

    def is_special(self, model_name:str):
        return model_name in self.special_model_name

    # normal get routines
    def get_ftp_host(self):
        return self.release_settings_dict['ftp_host']

    def get_ftp_username(self):
        return self.release_settings_dict['ftp_username']

    def get_ftp_password(self):
        return self.release_settings_dict['ftp_password']

    def get_model_version(self, model_name: str):
        return self.model_params[model_name]['fw_version']['version_tail']

    def get_model_repoes_and_branches(self, model_name:str):
        return self.model_repos_branch[model_name]

    def get_models_name_dict(self):
        return self.models_name_dict

    def get_models_dir_dict(self):
        return self.models_dir_dict

    # for parameters initialization
    def is_fw_src(self, p: Path):
        cust_dir = p.joinpath(self.CUST_INC_DIR)
        return cust_dir.exists()

    def get_fw_model_name(self, fw: Path):
        if fw.exists() is False:
            return None

        env_file = str(fw.absolute()) + '/env.h'
        if Path(env_file).exists() is False:
            return None

        model_name = None
        with open(env_file, mode='r') as f:
            for line in f:
                m = re.search('(?<=export MODEL_NAME=)[a-zA-Z0-9-]+', line)
                if m is not None:
                    model_name = m.group(0)
                    break

        return model_name

    def get_ftp_show_model_name(self, model_name):
        name = self.model_params[model_name]['show_model_name']['hori_line_with_slash'].replace('/', '-')
        if self.is_special(model_name) is False:
            if 'B1' == self.get_hw_version_tail(model_name):
                name = name + '-' + self.model_params[model_name]['hw_version']['version_tail']
        return name
        

    def init_model_params_dict(self):
        for (model_name, model_dir) in self.models_name_dict.items():
            cust = self.CUST_INC_DIR + model_name + '/cust_model.h'
            cust_file = model_dir.joinpath(cust)
            # init params dict
            params = {}
            params['fw_version'] = {}
            params['hw_version'] = {}
            params['show_model_name'] = {}
            params['mib_hw_version'] = ''
            params['mib_sw_version'] = {}
            with open(str(cust_file), mode='r') as f:
                bits = 0x00
                for line in f:
                    arr = str(line).split()
                    if len(arr) == 3:
                        value = arr[-1].strip('"')
                        if arr[1] == 'ISS_HW_VERSION':
                            params['hw_version']['version_dot'] = value
                            params['hw_version']['version_tail'] = value.split('.')[-1]
                            bits |= 0x01
                        elif arr[1] == 'ISS_FW_VERSION':
                            params['fw_version']['version_dot'] = value
                            params['fw_version']['version_hori_line'] = value.replace('.', '-')
                            params['fw_version']['version_tail'] = value.split('.')[-1]
                            bits |= (0x01 << 1)
                        elif arr[1] == 'CAMEO_PROJECT_SHOW_MODEL_NAME':
                            params['show_model_name']['hori_line_with_slash'] = value
                            params['show_model_name']['hori_line_no_slash'] = value.replace('/', '')
                            bits |= (0x01 << 2)
                        elif arr[1] == 'CAMEO_MIB_HW_VERSION':
                            params['mib_hw_version'] = value
                            bits |= (0x01 << 3)
                        elif arr[1] == 'CAMEO_MIB_SW_VERSION':
                            params['mib_sw_version']['version_hori_line'] = value
                            params['mib_sw_version']['version_dot'] = value.replace('-', '.')
                            bits |= (0x01 << 4)
                        if bits & 0x1f == 0x1f:
                            break
            self.model_params[model_name] = params

        # now we can init hex_path_dict
        for (model_name, model_dir) in self.models_name_dict.items():
            self.hex_path_dict[model_name] = {}
            self.hex_path_dict[model_name]['src'] = {}
            path_dict = {}
            base_name = self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
                  '-' + self.model_params[model_name]['hw_version']['version_tail'] + \
                  '-' + self.model_params[model_name]['fw_version']['version_hori_line']
            path_dict['fw_hex'] = {}
            path_dict['cmds_csv'] = {}
            path_dict['release_note_txt'] = {}
            path_dict['fw_mib'] = {}
            path_dict['fw_bin'] = {}
            path_dict['iss_exe'] = {}
            path_dict['all_hex'] = {}
            path_dict['fw_hex']['path'] = base_name + '.hex'
            path_dict['fw_hex']['is_backup'] = False # is this need copying to backup dir
            path_dict['fw_hex']['is_done'] = True  # this dict entry has done
            path_dict['cmds_csv']['path'] = 'non_backdoor_cmds.csv'
            path_dict['cmds_csv']['is_backup'] = False
            path_dict['cmds_csv']['is_done'] = True
            path_dict['release_note_txt']['path'] = base_name + '_FW_Release Note.txt'
            path_dict['release_note_txt']['is_backup'] = False
            path_dict['release_note_txt']['is_done'] = False
            path_dict['fw_mib']['path'] = \
                self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
                '-' + self.model_params[model_name]['mib_hw_version'] + \
                '-' + self.model_params[model_name]['mib_sw_version']['version_hori_line'] + '.mib'
            path_dict['fw_mib']['is_backup'] = False
            path_dict['fw_mib']['is_done'] = True
            path_dict['fw_bin']['path'] = base_name + '.bin'
            path_dict['fw_bin']['is_backup'] = True
            path_dict['fw_bin']['is_done'] = True
            path_dict['iss_exe']['path'] = 'ISS.exe'
            path_dict['iss_exe']['is_backup'] = True
            path_dict['iss_exe']['is_done'] = True
            path_dict['all_hex']['path'] = base_name + '-ALL.hex'
            path_dict['all_hex']['is_backup'] = True
            path_dict['all_hex']['is_done'] = True
            image_dir = 'image/' + self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
                             '-' + self.model_params[model_name]['hw_version']['version_tail']
            self.hex_path_dict[model_name]['base_name'] = {}
            for i in path_dict:
                self.hex_path_dict[model_name]['base_name'][i] = path_dict[i]['path']
            self.hex_path_dict[model_name]['base_name']['cmds_csv'] = base_name + '_cmds.csv'
            for (alias, name) in path_dict.items():
                # release notes are in other dirs, we will init it later after ftp params inited
                if alias != 'release_note_txt':
                    path_dict[alias]['path'] = str(model_dir.joinpath(image_dir).joinpath(path_dict[alias]['path']).absolute())

            self.hex_path_dict[model_name]['src'] = path_dict

            # extract prom and runtime checksum from hex and boot hex
            hex = model_dir.joinpath(image_dir).joinpath(path_dict['fw_hex']['path'])
            boot_hex = model_dir.joinpath(image_dir).joinpath(base_name + '-BOOT.hex')
            # runtime checksum
            with open(str(hex), 'rb') as bf:
                str_out = binascii.b2a_hex(bf.read()).decode()
                goto = str_out.find(self.CAMEOTAG_IN_HEX) + 32
                str_out_left = str_out[goto:]
                cameo_tag_start = str_out_left.find(self.CAMEOTAG_IN_HEX) + len(self.CAMEOTAG_IN_HEX) + 8
                cameo_tag_end = cameo_tag_start + 8
                runtime_checksum = str_out_left[cameo_tag_start:cameo_tag_end]
                self.model_params[model_name]['checksum_runtime'] = '0x' + runtime_checksum
            # prom_checksum
            with open(str(boot_hex), 'rb') as bf:
                str_out = binascii.b2a_hex(bf.read()).decode()
                cameo_tag_start = \
                    str_out.find(self.CAMEOTAG_IN_HEX) + len(self.CAMEOTAG_IN_HEX) + 8
                cameo_tag_end = cameo_tag_start + 8
                prom_checksum = str_out[cameo_tag_start:cameo_tag_end]
                self.model_params[model_name]['checksum_prom'] = '0x' + prom_checksum

            # init ftp path dict
            '''
            self.ftp_path_dict = {}
            {model_name :
                {
                    # for getting the last release notes
                    version_tail : <version_tail>
                    last_version_tail : <last_version_tail>
                    last_version_normal : <last_version_normal>
                    last_all_hex : <last_all_hex>
                    normal : <normal>,
                    backup : <backup>,
                    version_normal : <vd>,
                    version_backup : <vbd>
                }
            }
            '''
            # guess the last version name
            version_tail = self.model_params[model_name]['fw_version']['version_tail']
            self.ftp_path_dict[model_name] = {}
            self.ftp_path_dict[model_name]['version_tail'] = version_tail
            last_version_tail = self.get_pre_fw_version(version_tail)
            self.ftp_path_dict[model_name]['last_version_tail'] = last_version_tail
            self.ftp_path_dict[model_name]['version_normal'] = self.FTP_FIRMWARE_PATH + version_tail
            self.ftp_path_dict[model_name]['last_version_normal'] = self.FTP_FIRMWARE_PATH + last_version_tail
            self.ftp_path_dict[model_name]['last_all_hex'] = self.FTP_FIRMWARE_PATH + last_version_tail + '/AllHEX'
            self.ftp_path_dict[model_name]['version_backup'] = self.FTP_FIRMWARE_PATH + 'backup/' + version_tail
            
    def get_curr_fw_version(self, model_name:str):
        return self.model_params[model_name]['fw_version']['version_tail']

    def get_last_fw_hex_name(self, model_name:str):
        pre_fw_version_tail = self.get_pre_fw_version(self.model_params[model_name]['fw_version']['version_tail'])
        cur_fw_version_full = self.model_params[model_name]['fw_version']['version_hori_line']
        prefix = '-'.join(cur_fw_version_full.split('-')[0:-1])
        base_name = self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
              '-' + self.model_params[model_name]['hw_version']['version_tail'] + \
              '-' + prefix + '-' + pre_fw_version_tail + '.hex'
        return base_name

    def get_last_all_hex_name(self, model_name:str):
        pre_fw_version_tail = self.get_pre_fw_version(self.model_params[model_name]['fw_version']['version_tail'])
        cur_fw_version_full = self.model_params[model_name]['fw_version']['version_hori_line']
        prefix = '-'.join(cur_fw_version_full.split('-')[0:-1])
        base_name = self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
              '-' + self.model_params[model_name]['hw_version']['version_tail'] + \
              '-' + prefix + '-' + pre_fw_version_tail + '-ALL.hex'
        return base_name

    def get_last_release_note_name(self, model_name:str):
        pre_fw_version_tail = self.get_pre_fw_version(self.model_params[model_name]['fw_version']['version_tail'])
        cur_fw_version_full = self.model_params[model_name]['fw_version']['version_hori_line']
        prefix = '-'.join(cur_fw_version_full.split('-')[0:-1])
        base_name = self.model_params[model_name]['show_model_name']['hori_line_no_slash'] + \
              '-' + self.model_params[model_name]['hw_version']['version_tail'] + \
              '-' + prefix + '-' + pre_fw_version_tail + '_FW_Release Note.txt'
        return base_name

    def get_release_note_name(self, model_name:str):
        return self.hex_path_dict[model_name]['src']['release_note_txt']['path']

    def set_ftp_normal_path(self, model_name:str, path:str):
        self.ftp_path_dict[model_name]['normal'] = path

    def set_ftp_backup_path(self, model_name:str, path:str):
        self.ftp_path_dict[model_name]['backup'] = path

    def get_ftp_version_normal_path(self, model_name:str):
        return self.ftp_path_dict[model_name]['version_normal']

    def get_ftp_version_backup_path(self, model_name:str):
        return self.ftp_path_dict[model_name]['version_backup']

    def get_ftp_normal_path(self, model_name:str):
        return self.ftp_path_dict[model_name].get('normal')

    def get_ftp_backup_path(self, model_name:str):
        return self.ftp_path_dict[model_name].get('backup')

    def get_ftp_last_version_normal_path(self, model_name:str):
        return self.ftp_path_dict[model_name]['last_version_normal']

    def get_ftp_last_all_hex_path(self, model_name:str):
        return self.ftp_path_dict[model_name]['last_all_hex']

    def get_fw_hex_name(self, model_name:str):
        return self.model_params[model_name]['base_name']['fw_hex']

    def is_note_local(self, model_name:str):
        return self.ftp_path_dict[model_name].get('note_local')

    def set_last_release_note_path(self, model_name, path:str):
        self.ftp_path_dict[model_name]['last_release_note_path'] = {}
        self.ftp_path_dict[model_name]['last_release_note_path'] = path
        get = self.var_note_local.get()
        set = True
        if get == 0:
            set = False
        self.ftp_path_dict[model_name]['note_local'] = set
    
    def set_ftp_cur_all_hex_path(self, model_name, path:dict):
        self.ftp_path_dict[model_name]['cur_all_hex_path'] = {}
        self.ftp_path_dict[model_name]['cur_all_hex_path'] = path

    def get_last_release_note_path(self, model_name):
        return self.ftp_path_dict[model_name].get('last_release_note_path')

    def get_ftp_cur_all_hex_path(self, model_name):
        return self.ftp_path_dict[model_name].get('cur_all_hex_path')

    @staticmethod
    def get_pre_fw_version(version_tail: str):
        # guess the last version name
        if version_tail[-1].isalpha():
            if version_tail[-1] != 'A' and version_tail[-1] != 'a':
                last_version_tail = version_tail[0:-1] + chr(ord(version_tail[-1]) -1)
            else:
                last_version_tail = '%s%03d%s' % (version_tail[0], int(version_tail[1:-1]), version_tail[-1])
        elif version_tail[0] == 'B':
            last_version_tail = 'B%03d' % (int(version_tail[1:]) - 1)
        else:
            last_version_tail = 'B' + version_tail[1:]
        return last_version_tail

    def init_model_repo_branch_dict(self):
        if self.models_dir_dict:
            for (k,v) in self.models_dir_dict.items():
                sh = 'make -C %s git-info --no-print-directory' % str(k.absolute())
                (status, output) = subprocess.getstatusoutput(sh)
                # by the way, update git_info in model_params dict
                lines = output.split('\n')[2:-1]
                git_info = ''
                git_info += '(model)  (repertory)          (branch)             (version)      (commit by)      (date)\n'
                for l in lines:
                    larr = l.split()
                    if larr[0] in self.REPO_MODEL:
                        git_info += ' %-8s %-20s%-21s%-16s%-16s%-s\n'%(larr[0], larr[1], larr[2], larr[3][5:], larr[4][5:], ('<'+re.search(r'(.*)<(.*)>(.*)', l).group(2)+'>'))
                self.model_params[v]['git_info'] = git_info
                self.model_params[v]['bug_fixed'] = ''
                self.model_params[v]['new_feature'] = ''
                self.model_params[v]['new_feature_locked'] = False
                self.model_params[v]['bug_fixed_locked'] = False
                self.model_repos_branch[v] = {}
                for line in output.split('\n'):
                    arr = line.strip().split()
                    if arr[0] in self.REPO_MODEL:
                        self.model_repos_branch[v][arr[0]] = (arr[1], arr[2])

    def if_need_release(self, model_name:str):
        return self.release_settings_dict[model_name]['need_release'].get()

    def copy_from_src_to_dst(self):
        for model_name in self.hex_path_dict:
            if self.release_settings_dict[model_name]['need_release'].get() is not False:
                for (it, vl) in self.hex_path_dict[model_name]['src'].items():
                    if vl['is_done']:
                        # cp all release files to this directory
                        shutil.copyfile(vl['path'], self.hex_path_dict[model_name]['dst'][it])

    def init_release_note_tmp_file_path(self):
        for model_name in self.models_name_dict:
            if self.if_need_release(model_name):
                note = self.get_last_release_note_path(model_name)
                if note is not None:
                    self.hex_path_dict[model_name]['src']['release_note_txt']['path'] = \
                        '/tmp/' + note.split('/')[-1]
                    self.hex_path_dict[model_name]['src']['release_note_txt']['is_backup'] = False
                    self.hex_path_dict[model_name]['src']['release_note_txt']['is_done'] = False

    def get_release_content(self, model_name:str):
        text = []
        today = datetime.date.today()
        text.append('________________________________________________________________________________\n')
        text.append('        RELEASE VERSION  : %s\n' %(self.model_params[model_name]['fw_version']['version_dot']) )
        text.append('        RELEASE DATE     : %04d/%02d/%02d\n' %(today.year, today.month, today.day))
        text.append('        RELEASEE BY      : %s\n' %(self.release_settings_dict['release_by']))
        text.append('________________________________________________________________________________\n\n')

        text.append('[CHECK-CODE]    : \n')
        text.append('[CHECKSUM]      : [N/A]\n')
        text.append('[PART-NO]       : [N/A]\n')
        text.append('[GIT]           :\n%s\n\n' %(self.model_params[model_name]['git_info']))

        text.append('[TMPL-VER]      : [N/A]\n')
        text.append('[ECN-NO]        : [N/A]\n')
        text.append('[TP-TIME]       : [N/A]\n')
        text.append('[FW-VER]        : %s\n' %(self.model_params[model_name]['fw_version']['version_dot']))
        text.append('[PLM-NO]        : [N/A]\n')
        text.append('________________________________________________________________________________\n\n')

        text.append('New Feature:\n')
        text.append('-------------\n')
        text.append('%s\n\n' %(self.model_params[model_name]['new_feature']))

        text.append('Bug Fixed:\n')
        text.append('-------------\n')
        text.append('%s\n\n' %(self.model_params[model_name]['bug_fixed']))

        text.append('Changed:\n')
        text.append('-------------\n')
        text.append('N/A\n\n')


        text.append('Note:\n')
        text.append('-------------\n\n')

        text.append('Version :\n')
        text.append('    HW      : %s\n' %(self.model_params[model_name]['hw_version']['version_tail']))
        text.append('    PROM    : 1.01.033\n')
        text.append('    Runtime : %s\n\n' %(self.model_params[model_name]['fw_version']['version_dot']))

        text.append('Checksum :\n')
        text.append('    PROM    : %s\n' %(self.model_params[model_name]['checksum_prom']))
        text.append('    Runtime : %s\n\n' %(self.model_params[model_name]['checksum_runtime']))


        text.append('Known issues:\n')
        text.append('-------------\n\n')
        return text


    def generate_release_note_in_tmp_dir(self):
        for model_name in self.models_name_dict:
            if self.if_need_release(model_name):
                if self.is_ftp_release_note_reachable(model_name) is True:
                    pre_note_file_path = self.hex_path_dict[model_name]['src']['release_note_txt']['path']
                    with open(pre_note_file_path, 'r') as fp:
                            content = fp.readlines()
                    head = content[0:10]
                    history = content[10:]
                    with open (pre_note_file_path, 'w') as fp:
                        fp.writelines(head)
                        fp.writelines(self.get_release_content(model_name))
                        fp.writelines(history)

                    model_dst_dir = Path(os.path.expanduser(self.release_settings_dict['release_to'])).joinpath(
                        self.model_params[model_name]['fw_version']['version_tail']).joinpath(model_name)
                    backup_dir = model_dst_dir.joinpath('backup')
                    if self.hex_path_dict[model_name]['src']['release_note_txt']['is_backup']:
                        self.hex_path_dict[model_name]['dst']['release_note_txt'] = \
                            str(backup_dir.joinpath(self.hex_path_dict[model_name]['base_name']['release_note_txt']).absolute())
                    else:
                        self.hex_path_dict[model_name]['dst']['release_note_txt'] = \
                            str(model_dst_dir.joinpath(self.hex_path_dict[model_name]['base_name']['release_note_txt']).absolute())
                    self.hex_path_dict[model_name]['src']['release_note_txt']['is_done'] = True

    def download_release_note_to_tmp(self):
        for model_name in self.models_name_dict:
            if self.if_need_release(model_name):
                pt = self.get_last_release_note_path(model_name)
                if self.is_ftp_release_note_reachable(model_name) is True:
                    if self.ftp_path_dict[model_name]['note_local']:
                        shutil.copy(pt, '/tmp')
                    else:
                        ftp = RFtp(self.get_ftp_host())
                        ftp.login(self.get_ftp_username(), self.get_ftp_password())
                        dir = os.path.dirname(pt)
                        nm = pt.split('/')[-1]
                        ftp.download_file('/tmp', dir, nm)
                        ftp.close()

    def release(self):
        self.init_release_note_tmp_file_path()
        self.download_release_note_to_tmp()
        self.generate_release_note_in_tmp_dir()
        self.copy_from_src_to_dst()

    def init_release_settings(self):
        # called after user entered release to and release by ...
        # hex_path_dict.dst:
        for model_name in self.hex_path_dict:
            if self.release_settings_dict[model_name]['need_release'].get() is not False:
                model_dst_dir = Path(os.path.expanduser(self.release_settings_dict['release_to'])).joinpath(
                    self.model_params[model_name]['fw_version']['version_tail']).joinpath(model_name)
                if model_dst_dir.exists() is False:
                    model_dst_dir.mkdir(parents=True)
                # backup dir
                backup_dir = model_dst_dir.joinpath('backup')
                if backup_dir.exists() is False:
                    backup_dir.mkdir(parents=True)
                # init dst params
                self.hex_path_dict[model_name]['dst'] = {}
                for (it, vl) in self.hex_path_dict[model_name]['src'].items():
                    if vl['is_done']:
                        if vl['is_backup']:
                            self.hex_path_dict[model_name]['dst'][it] = \
                                str(backup_dir.joinpath(self.hex_path_dict[model_name]['base_name'][it]).absolute())
                        else:
                            self.hex_path_dict[model_name]['dst'][it] = \
                                str(model_dst_dir.joinpath(self.hex_path_dict[model_name]['base_name'][it]).absolute())
            
    def init_release_parameters(self):
            '''
            After getting release directory, initialize the release parameters and show progress bar.
    
            Do following steps:
            1. detect fw source tree in release directory
            2. detect current git repos and branches in specified source tree directores
            3. strip all fw specified parameters from source tree: fw name ,fw version, checksum ...
            '''
            # detect model directories in release directory
            for x in self.release_dir.iterdir():
                if self.is_fw_src(x):
                    model_name = self.get_fw_model_name(x)
                    self.models_dir_dict[x] = model_name
                    self.models_name_dict[model_name] = x
            
            # after model name and path initialized, we can get other parameters now
            self.init_dict()



class UiController:
    ''' Generate UI '''
    def __init__(self):
        self.root = None
        self.release_dir = None
        self.release_controller = None
        self.bar = None
        self.thread = None
        self.errmsg_label = None
        self.errmsg_label_in_model_setting = None

    def show_errmsg(self, msg: str):
        if self.errmsg_label:
            self.errmsg_label['text'] = msg
            # self.errmsg_label.pack()

    def show_model_errmsg(self, msg: str):
        self.var_model_errmsg.set(msg)

    def adjust_root(self):
        self.root.update()
        size_str = '%sx%s' % (self.root.winfo_width(), self.root.winfo_height())
        self.root.geometry(size_str +'+500+400')

    def onValidate(self, d, i, P, s, S, v, V, W):
        self.release_to.delete('0', 'end')
        self.release_to.insert('end', '%s/Firmwares' % P)
        return True

    def init_window(self):
        self.root = tk.Tk()
        ttk.Label(self.root, text='Release directory:').grid(row=0, column=0)

        # valid percent substitutiopt=ons (from the Tk entry man page)
        # %P = value of the entry if the edit is allowed
        vcmd = (self.root.register(self.onValidate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')

        self.entry = ttk.Entry(self.root, validate="key", validatecommand=vcmd)
        self.entry.grid(row=0, column=1)

        # release settings
        release_setting = ttk.LabelFrame(self.root, text='Release settings')
        release_setting.grid(row=1, column=0, padx=5, pady=5, columnspan=2)
        ttk.Label(release_setting, text='Release By:').grid(row=0, column=0, sticky='E')
        self.release_by = ttk.Entry(release_setting)
        self.release_by.grid(row=0, column=1)

        ttk.Label(release_setting, text='Release To:').grid(row=1, column=0, sticky='E')
        self.release_to = ttk.Entry(release_setting)
        self.release_to.grid(row=1, column=1)

        ttk.Label(release_setting, text='Ftp Host:').grid(row=2, column=0, sticky='E')
        self.var_ftp_host=tk.StringVar(value=str(ReleaseController.FTP_HOST))
        self.ftp_host = ttk.Entry(release_setting, textvariable=self.var_ftp_host)
        self.ftp_host.grid(row=2, column=1)


        ttk.Label(release_setting, text='Ftp Username:').grid(row=3, column=0, sticky='E')
        self.var_ftp_username=tk.StringVar(value=str(ReleaseController.FTP_USERNAME))
        self.ftp_username = ttk.Entry(release_setting, textvariable=self.var_ftp_username)
        self.ftp_username.grid(row=3, column=1)


        ttk.Label(release_setting, text='Ftp Password:').grid(row=4, column=0, sticky='E')
        self.var_ftp_password=tk.StringVar(value=str(ReleaseController.FTP_PASSWORD))
        self.ftp_password = ttk.Entry(release_setting, textvariable=self.var_ftp_password)
        self.ftp_password.grid(row=4, column=1)

        ttk.Button(self.root, text='OK', command=
            self.basic_release_setting_confirmed).grid(row=2, column=1, sticky='W')
        self.errmsg_label = ttk.Label(self.root, text='', foreground='red')
        self.errmsg_label.grid(row=3, column=0, columnspan=2, sticky='W')
        self.adjust_root()
        self.root.mainloop()
        
    def model_setting_confirmed(self, model_name:str):
        # download the pre-version release note to make current version's
        self.show_model_errmsg('')
        fw_dir = self.var_ftp_fw_dir.get()
        backup_dir = self.var_ftp_backup_dir.get()
        cur_all_hex_path = self.var_ftp_all_hex_path.get()
        last_release_note_path = os.path.expanduser(self.var_ftp_last_release_note_path.get())

        if len(fw_dir) == 0 or len(backup_dir) == 0 or len(last_release_note_path) == 0:
            self.show_model_errmsg('The path should be filled')
            return

        if len(fw_dir) > 0:
            self.release_controller.set_last_release_note_path(model_name, last_release_note_path)
            self.release_controller.set_ftp_normal_hex_reachable(model_name, True)
        if len(backup_dir) > 0:
            self.release_controller.set_ftp_normal_path(model_name, fw_dir)
            self.release_controller.set_ftp_backup_hex_reachable(model_name, True)
        if len(last_release_note_path) > 0:
            self.release_controller.set_ftp_backup_path(model_name, backup_dir)
            self.release_controller.set_ftp_release_note_reachable(model_name, True)
        if len(cur_all_hex_path) > 0:
            self.release_controller.set_ftp_cur_all_hex_path(model_name, cur_all_hex_path)
            self.release_controller.set_ftp_all_hex_reachable(model_name, True)
        new_feature_content = self.new_feature_text.get('1.0', tk.END)
        bug_fixed_content = self.bug_fixed_text.get('1.0', tk.END)
        if len(new_feature_content) > 1:
            self.release_controller.set_new_feature(model_name, new_feature_content)
        if len(bug_fixed_content) > 1:
            self.release_controller.set_bug_fixed(model_name, bug_fixed_content)
        self.pop_window.destroy()


    def show_model_settings(self, name: str):
        ''' All settings for designated model '''
        self.pop_window = tk.Toplevel(master=self.root)
        self.pop_window.title(name)
        self.pop_window.transient(self.root)
        self.pop_window.grab_set()
        # branches
        branch_setting = ttk.LabelFrame(self.pop_window, text='Branch Settings')
        branch_setting.grid(row=0, column=0, padx=2, pady=5, sticky='NSWE')
        repo_br_dict = self.release_controller.get_model_repoes_and_branches(name)
        rowidx = 0
        for model in repo_br_dict:
            ttk.Label(branch_setting, text=repo_br_dict[model][0]).grid(row=rowidx, column=0, sticky='W', padx=5)
            ttk.Label(branch_setting, text=repo_br_dict[model][1]).grid(row=rowidx, column=1, sticky='W', padx=5)
            rowidx += 1
        
        version_setting = ttk.LabelFrame(self.pop_window, text='Version Settings')
        version_setting.grid(row=0, column=1, padx=2, pady=5, sticky='NSWE')
        ttk.Label(version_setting, text='Version: ').grid(row=0, column=0, sticky='W')
        ttk.Label(version_setting, text=self.release_controller.get_model_version(name)).grid(row=0, column=1, sticky='W')

        ftp_setting = ttk.LabelFrame(self.pop_window, text='Ftp Settings')
        ftp_setting.grid(row=1, column=0, padx=5, pady=5, columnspan=2, sticky='WE')
        ttk.Button(ftp_setting, text='Click ME to auto detect the path', command=lambda x=name: self.auto_detect(x)).grid(row=0, column=0, columnspan=2, sticky='EW')
        self.var_model_errmsg = tk.StringVar(value='')
        self.errmsg_label_in_model_setting = ttk.Label(self.pop_window, textvariable=self.var_model_errmsg, foreground='red')
        self.errmsg_label_in_model_setting.grid(row=5, column=0, columnspan=2)

        self.var_ftp_fw_dir = tk.StringVar(value='')
        self.var_ftp_backup_dir = tk.StringVar(value='')
        self.var_ftp_last_release_note_path = tk.StringVar(value='')
        self.var_ftp_all_hex_path = tk.StringVar(value='')
        var = self.release_controller.get_last_release_note_path(name)
        if var is not None:
            self.var_ftp_last_release_note_path.set(value=var)
        
        cur_all_hex = self.release_controller.get_ftp_cur_all_hex_path(name)
        if cur_all_hex is not None:
            self.var_ftp_all_hex_path.set(value=cur_all_hex)

        normal = self.release_controller.get_ftp_normal_path(name)
        if normal is not None:
            self.var_ftp_fw_dir.set(value=normal)
        
        backup = self.release_controller.get_ftp_backup_path(name)
        if backup is not None:
            self.var_ftp_backup_dir.set(value=backup)
        
        ttk.Label(ftp_setting, text='Firmware Directory: ').grid(row=1, column=0, sticky='E')
        ttk.Label(ftp_setting, text='Backup Directory: ').grid(row=2, column=0, sticky='E')
        ttk.Label(ftp_setting, text='All Hex Directory: ').grid(row=3, column=0, sticky='E')
        ttk.Label(ftp_setting, text='Last Release Note: ').grid(row=4, column=0, sticky='E')
        res = self.release_controller.is_note_local(name)
        val = 0
        if res is None or res is True:
            val = 1
        self.release_controller.var_note_local = tk.BooleanVar(value=val)
        ttk.Checkbutton(ftp_setting, text='Use local release note path', variable=self.release_controller.var_note_local).grid(row=5, column=1, sticky='E')
        tk.Entry(ftp_setting, textvariable=self.var_ftp_fw_dir, width=55).grid(row=1, column=1)
        tk.Entry(ftp_setting, textvariable=self.var_ftp_backup_dir, width=55).grid(row=2, column=1)
        tk.Entry(ftp_setting, textvariable=self.var_ftp_all_hex_path, width=55).grid(row=3, column=1)
        tk.Entry(ftp_setting, textvariable=self.var_ftp_last_release_note_path, width=55).grid(row=4, column=1)

        # new feature and fixed bug
        new_feature = ttk.LabelFrame(self.pop_window, text='New Feature')
        new_feature.grid(row=2, column=0, padx=5, pady=5, columnspan=2, sticky='WE')
        self.new_feature_text = tk.Text(new_feature, height=8)
        self.new_feature_text.insert('insert', self.release_controller.get_new_feature(name))
        self.new_feature_text.pack()

        bug_fixed = ttk.LabelFrame(self.pop_window, text='Bug Fixed')
        bug_fixed.grid(row=3, column=0, padx=5, pady=5, columnspan=2, sticky='WE')
        self.bug_fixed_text = tk.Text(bug_fixed, height=8)
        self.bug_fixed_text.insert('insert', self.release_controller.get_bug_fixed(name))
        self.bug_fixed_text.pack()

        ttk.Button(self.pop_window, text='OK', command=lambda x=name: self.model_setting_confirmed(name)).grid(row=4, column=1, sticky='E')
        self.adjust_top_win(self.pop_window)

    def check_if_detect_done(self):
        if self.thread and self.thread.is_alive():
            self.root.after(10, self.check_if_detect_done)
        else:
            self.stop_progress_bar()

    def start_detect(self, name:str):
        ftp = RFtp(self.release_controller.get_ftp_host())
        ftp.login(self.release_controller.get_ftp_username(), self.release_controller.get_ftp_password())
        current_version_normal_path = self.release_controller.get_ftp_version_normal_path(name)
        current_version_backup_path = self.release_controller.get_ftp_version_backup_path(name)
        
        model_dir_name = self.release_controller.get_ftp_show_model_name(name)
        ftp_model_normal_path = ftp.search_and_get_dir_path(current_version_normal_path, model_dir_name)
        ftp_model_backup_path = ftp.search_and_get_dir_path(current_version_backup_path, model_dir_name)
        if ftp_model_normal_path is not None:
            self.var_ftp_fw_dir.set(ftp_model_normal_path)
        if ftp_model_backup_path is not None:
            self.var_ftp_backup_dir.set(ftp_model_backup_path)

        last_fw_hex_name = self.release_controller.get_last_fw_hex_name(name)
        last_releasenote_name = self.release_controller.get_last_release_note_name(name)
        last_all_hex_name = self.release_controller.get_last_all_hex_name(name)
        last_version_normal_path = self.release_controller.get_ftp_last_version_normal_path(name)
        last_all_hex_path = self.release_controller.get_ftp_last_all_hex_path(name)
        ftp_last_fw_hex = ftp.search_and_get_file_path(last_version_normal_path, last_fw_hex_name)
        ftp_last_all_hex = ftp.search_and_get_file_path(last_all_hex_path, last_all_hex_name)
        ftp.close()
        if ftp_last_fw_hex is not None:
            dir = os.path.dirname(ftp_last_fw_hex)
            self.var_ftp_last_release_note_path.set(dir + '/' + last_releasenote_name)
            self.release_controller.var_note_local.set(0)
        if ftp_last_all_hex is not None:
            dir = os.path.dirname(ftp_last_all_hex)
            curr_fw_version = self.release_controller.get_curr_fw_version(name)
            pre_fw_version = self.release_controller.get_pre_fw_version(curr_fw_version)
            curr_all_hex_path = dir.replace(pre_fw_version, curr_fw_version)
            curr_all_hex_name = last_all_hex_name.replace(pre_fw_version, curr_fw_version)
            self.var_ftp_all_hex_path.set(curr_all_hex_path)

    def auto_detect(self, name:str):
        self.show_init_progress_bar('Wait...')
        self.thread = thr.Thread(target=self.start_detect, args=(name,))
        self.thread.start()
        self.root.after(10, self.check_if_detect_done)

    def create_new_feature_bug_fixed_for_all_models(self):
        self.pop_window = tk.Toplevel(master=self.root)
        self.pop_window.title('New Feature and Bug Fixed')
        self.pop_window.transient(self.root)
        self.pop_window.grab_set()

        # new feature and fixed bug
        new_feature = ttk.LabelFrame(self.pop_window, text='New Feature')
        new_feature.grid(row=0, column=0, padx=5, pady=5)
        self.new_feature_text = tk.Text(new_feature, height=8)
        self.new_feature_text.insert('insert', self.release_controller.get_global_new_feature())
        self.new_feature_text.grid(row=0, column=0)

        bug_fixed = ttk.LabelFrame(self.pop_window, text='Bug Fixed')
        bug_fixed.grid(row=1, column=0, padx=5, pady=5)
        self.bug_fixed_text = tk.Text(bug_fixed, height=8)
        self.bug_fixed_text.insert('insert', self.release_controller.get_global_bug_fixed())
        self.bug_fixed_text.grid(row=1, column=0)

        confirm_button = ttk.Button(self.pop_window, text='OK', command=self.set_new_feature_bug_fixed_for_all_models)
        confirm_button.grid(row=2, column=0, sticky='E')

    def set_new_feature_bug_fixed_for_all_models(self):
        new_feature_content = self.new_feature_text.get('1.0', tk.END)
        bug_fixed_content = self.bug_fixed_text.get('1.0', tk.END)
        if len(new_feature_content) <= 1 and len(bug_fixed_content) <= 1:
            self.pop_window.destroy()
            return
        if len(new_feature_content) > 1:
            self.release_controller.set_global_new_feature(new_feature_content)
        if len(bug_fixed_content) > 1:
            self.release_controller.set_global_bug_fixed(bug_fixed_content)
        for name in self.release_controller.models_name_dict:
            if self.release_controller.is_new_feature_locked(name) is False:
                current_new_feature = self.release_controller.get_new_feature(name)
                if len(new_feature_content) > 1 and len(current_new_feature) <= 1:
                    self.release_controller.set_new_feature(name, new_feature_content)

            if self.release_controller.is_bug_fixed_locked(name) is False:
                current_bug_fixed = self.release_controller.get_bug_fixed(name)
                if len(bug_fixed_content) > 1 and len(current_bug_fixed) <= 1:
                    self.release_controller.set_bug_fixed(name, bug_fixed_content)
        self.pop_window.destroy()
            

    def create_release_window(self):
        '''Create main window to collect release parameters'''
        # destroy root first
        if self.root:
            self.root.destroy()

        self.root = tk.Tk()
        self.root.title('Release')
        model_select = ttk.LabelFrame(self.root, text='Select models to release')
        model_select.grid(row=0, column=0, padx=5, pady=5)
        # create widgets
        # get model directories array from release controller
        model_name_dict = self.release_controller.get_models_name_dict()
        rowidx = 0
        for i in model_name_dict:
            self.release_controller.release_settings_dict[i] = {}
            # create check boxes
            self.release_controller.release_settings_dict[i]['need_release'] = tk.BooleanVar(value=True)
            ttk.Checkbutton(model_select, text=i, variable=\
                self.release_controller.release_settings_dict[i]['need_release']).grid(column=0, row=rowidx, sticky='W')
            ttk.Button(model_select, text='Model Settings', 
                command=lambda x=i: self.show_model_settings(x)).grid(column=1, row=rowidx, sticky='W')
            rowidx += 1

        ttk.Button(self.root, text='Set New Feature And Bug Fixed', command=self.create_new_feature_bug_fixed_for_all_models).grid(row=1, column=0, sticky='WE')
        ttk.Button(self.root, text='Start', command=self.models_chosen_confirmed).grid(row=2, column=0, sticky='E')

        self.adjust_root()
        self.root.mainloop()

    def models_chosen_confirmed(self):
        self.release_controller.init_release_settings()
        self.show_init_progress_bar('Wait...')
        self.thread = thr.Thread(target=self.release_controller.release)
        self.thread.start()
        self.root.after(10, self.check_if_release_done)

    def adjust_top_win(self, top_win: tk.Toplevel):
        top_win.update()
        size_str = '%sx%s' % (top_win.winfo_width(), top_win.winfo_height())
        top_win.geometry(size_str +'+400+150')

    def show_init_progress_bar(self, prompt: str):
        self.progress_top_win = tk.Toplevel(master=self.root)
        self.progress_top_win.title('Wait...')
        self.progress_top_win.transient(self.root)
        self.progress_top_win.grab_set()
        self.bar = ttk.Progressbar(self.progress_top_win, length=200, value=0, mode='indeterminate')
        self.bar.pack()
        self.adjust_top_win(self.progress_top_win)
        self.bar.start(interval=10)
    
    def stop_progress_bar(self):
        self.bar.stop()
        self.bar.destroy()
        self.progress_top_win.destroy()

    def create_upload_window(self):
        self.pop_window = tk.Toplevel(master=self.root)
        self.pop_window.title('Upload files to FTP')
        self.pop_window.transient(self.root)
        self.pop_window.grab_set()
        tree = ttk.Treeview(self.pop_window)
        tree.grid(column=0, row=0)
        tree.column('#0', width=700)
        for name in self.release_controller.models_name_dict:
            if self.release_controller.if_need_release(name):
                tree.insert('', 'end', name, text=name)
                list = self.release_controller.get_model_hex_list(name)
                for i in list:
                    tree.insert(name, 'end', text=i)

    def check_if_release_done(self):
        if self.thread and self.thread.is_alive():
            self.root.after(10, self.check_if_release_done)
        else:
            self.stop_progress_bar()
            self.create_upload_window()

    def check_if_release_init_done(self):
        if self.thread and self.thread.is_alive():
            self.root.after(10, self.check_if_release_init_done)
        else:
            self.stop_progress_bar()
            self.create_release_window()

    def basic_release_setting_confirmed(self):
        '''Got release directory'''
        if self.entry.get() == '':
            self.show_errmsg('Please input release directory!')
            return
        self.release_dir = Path(os.path.expanduser(self.entry.get()))
        
        if self.release_dir.exists() is False:
            self.show_errmsg(self.release_dir._str + ' not exists!')
            return

        if self.release_by.get() == '' or self.release_to.get() == '':
            self.show_errmsg('Release By or Release To can not be empty')
            return
        
        if self.ftp_host.get() == '':
            self.show_errmsg('Ftp Host can not be empty')
            return

        if self.ftp_username.get() == '':
            self.show_errmsg('Ftp Username can not be empty')
            return

        if self.ftp_password.get() == '':
            self.show_errmsg('Ftp Password can not be empty')
            return

        full_path_str = os.path.expanduser(self.release_to.get())
        if Path(full_path_str).exists() is False:
            self.show_errmsg('Release To directory is not exists')
            return

        self.show_init_progress_bar('Init parameters ...')
        self.release_controller = ReleaseController(self.release_dir)
        self.release_controller.release_settings_dict['release_by'] = self.release_by.get()
        self.release_controller.release_settings_dict['release_to'] = full_path_str
        self.release_controller.release_settings_dict['ftp_host'] = self.ftp_host.get()
        self.release_controller.release_settings_dict['ftp_username'] = self.ftp_username.get()
        self.release_controller.release_settings_dict['ftp_password'] = self.ftp_password.get()

        self.thread = thr.Thread(target=self.release_controller.init_release_parameters)
        self.thread.start()
        self.root.after(10, self.check_if_release_init_done)



if __name__ == '__main__':
    ui_controller = UiController()
    ui_controller.init_window()
